package com.lagou.edu.utils;

import com.alibaba.druid.util.StringUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

public class ClasspathPackageScanner {

    public static List<String> getFullyQualifiedClassNameList(List<String> basePackages, ClassLoader cl) throws IOException {
        List<String> fullyQualifiedClassNames = new ArrayList<>();
        for (String basePackage : checkPackages(basePackages)) {
            List<String> fullyQualifiedClassNameList = getFullyQualifiedClassNameList(basePackage.trim(), cl);
            fullyQualifiedClassNames.addAll(fullyQualifiedClassNameList);
        }
        return fullyQualifiedClassNames;
    }

    /**
     * 获取指定包下的所有字节码文件的全类名
     */
    public static List<String> getFullyQualifiedClassNameList(String basePackage, ClassLoader cl) throws IOException {
        return doScan(basePackage, cl, new ArrayList<String>());
    }

    /**
     * 排重、检测package父子关系，避免多次扫描
     *
     * @param basePackages 扫包路径
     * @return 返回全部有效的包路径信息
     */
    private static Set<String> checkPackages(List<String> basePackages) {

        Set<String> packages = new HashSet<>(basePackages);
        //排重路径
        for (String packageStr : packages.toArray(new String[0])) {
            if (StringUtils.isEmpty(packageStr) || packageStr.equals(".") || packageStr.startsWith(".")) {
                continue;
            }
            if (packageStr.endsWith(".")) {
                packageStr = packageStr.substring(0, packageStr.length() - 1);
            }
            Iterator<String> packageIte = packages.iterator();
            boolean needAdd = true;
            while (packageIte.hasNext()) {
                String pack = packageIte.next();
                if (packageStr.startsWith(pack + ".")) {
                    //如果待加入的路径是已经加入的pack的子集，不加入
                    needAdd = false;
                } else if (pack.startsWith(packageStr + ".")) {
                    //如果待加入的路径是已经加入的pack的父集，删除已加入的pack
                    packageIte.remove();
                }
            }
            if (needAdd) {
                packages.add(packageStr);
            }
        }
        return packages;
    }

    /**
     * doScan函数
     *
     * @param basePackage
     * @param nameList
     * @return
     * @throws IOException
     */
    private static List<String> doScan(String basePackage, ClassLoader cl, List<String> nameList) throws IOException {
        String splashPath = StringUtil.dotToSplash(basePackage);
        URL url = cl.getResource(splashPath);   //file:/D:/WorkSpace/java/ScanTest/target/classes/com/scan
        String filePath = StringUtil.getRootPath(url);
        List<String> names = null;
        if (isJarFile(filePath)) {
            names = readFromJarFile(filePath, splashPath);
        } else {
            names = readFromDirectory(filePath);
        }
        for (String name : names) {
            if (isClassFile(name)) {
                nameList.add(toFullyQualifiedName(name, basePackage));
            } else {
                doScan(basePackage + "." + name, cl, nameList);
            }
        }
        return nameList;
    }

    private static String toFullyQualifiedName(String shortName, String basePackage) {
        StringBuilder sb = new StringBuilder(basePackage);
        sb.append('.');
        sb.append(StringUtil.trimExtension(shortName));
        //打印出结果
//        System.out.println(sb.toString());
        return sb.toString();
    }

    private static List<String> readFromJarFile(String jarPath, String splashedPackageName) throws IOException {
        JarInputStream jarIn = new JarInputStream(new FileInputStream(jarPath));
        JarEntry entry = jarIn.getNextJarEntry();
        List<String> nameList = new ArrayList<String>();
        while (null != entry) {
            String name = entry.getName();
            if (name.startsWith(splashedPackageName) && isClassFile(name)) {
                nameList.add(name);
            }

            entry = jarIn.getNextJarEntry();
        }

        return nameList;
    }

    private static List<String> readFromDirectory(String path) {
        File file = new File(path);
        String[] names = file.list();

        if (null == names) {
            return null;
        }

        return Arrays.asList(names);
    }

    private static boolean isClassFile(String name) {
        return name.endsWith(".class");
    }

    private static boolean isJarFile(String name) {
        return name.endsWith(".jar");
    }

    public static void main(String[] args) throws IOException {

        List<String> fullyQualifiedClassNameList =
                ClasspathPackageScanner.getFullyQualifiedClassNameList("com.lagou.edu", ClasspathPackageScanner.class.getClassLoader());
        for (String s : fullyQualifiedClassNameList) {
            System.out.println(s);
        }

        System.out.println("*************************");
        List<String> basePackages = new ArrayList<>();
        basePackages.add("com.lagou.edu");
        basePackages.add("com.lagou.edu.dao.impl");
        basePackages.add("com.lagou.edu.service.impl");
        basePackages.add("com.lagou.edu.utils");
        fullyQualifiedClassNameList =
                ClasspathPackageScanner.getFullyQualifiedClassNameList(basePackages, ClasspathPackageScanner.class.getClassLoader());
        for (String s : fullyQualifiedClassNameList) {
            System.out.println(s);
        }
    }
}
